home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1999-01-16 | 39.9 KB | 1,327 lines | [ TEXT/ALFA]
## -*-Tcl-*- # ################################################################### # Vince's Additions - an extension package for Alpha # # FILE: "package.tcl" # created: 2/8/97 {6:15:10 pm} # last update: 16/1/1999 {10:22:13 am} # Author: Vince Darley # E-mail: <darley@fas.harvard.edu> # mail: Division of Engineering and Applied Sciences, Harvard University # Oxford Street, Cambridge MA 02138, USA # www: <http://www.fas.harvard.edu/~darley/> # # Copyright (c) 1997-1998 Vince Darley, all rights reserved # # How to ensure packages are loaded in the correct order? # (some may require Vince's Additions). Here perhaps we could # just use a Tcl8-like-approach: introduce a 'package' command # and have stuff like 'package Name 1.0 script-to-load'. # Then a package can just do 'package require Othername' to ensure # it is loaded. I like this approach. # # How to initialise each package at startup? If we use the above # scheme, then the startup script is purely a sequence of # 'package require Name' commands. The file 'prefs.tcl' is then # purely for user-meddling. Packages do not need to store anything # there. Sounds good to me. # # How to uninstall things? One approach here is a # 'package uninstall Name' command. Nice packages would provide # this. # # We need a default behaviour too. Some packages require no # installation at all (except placing in a directory), others # require sourcing, others need to add something to a menu. How # much of this should be automated and how much is up to the # package author? # # ---- # # The solution below is to imitate Tcl 8. There is a 'package' # mechanism. There exists a index::feature() array which gives for # each package the means to load it --- a procedure name or a # 'source file' command. The package index is compiled # automatically by recursively scanning all files in the # Packages directory for 'package name version do-this' # commands. # # There's also 'package names', 'package exists name', and an # important 'package require name version' which allows one # package to autoload another... # # Pros of this approach: many packages, which would otherwise # require an installation procedure, now can be just dropped # in to the packages directory and they're installed! (After # rebuilding the package index). This is because 'package' # can declare a snippet of code, an addition to a menu etc… # ---- # # Thanks to Tom Fetherston for some improvements here. # ################################################################### ## namespace eval package {} namespace eval date {} namespace eval remote {} ## # ------------------------------------------------------------------------- # # "alpha::findAllExtensions" -- # # package require all extensions the user has activated # ------------------------------------------------------------------------- ## proc alpha::findAllExtensions {} { global global::features index::feature alpha::systempackages # this carries out the existence part of each feature foreach m [array names index::feature] { if {[lsearch -exact [set alpha::systempackages] $m] != -1} { continue } set info [set index::feature($m)] if {[string trim [lindex $info 3]] != ""} { try::level \#0 [lindex [set index::feature($m)] 3] -reporting log -while "initialising $m" set index::feature($m) [lreplace [set index::feature($m)] 3 3 ""] } } # remove any package which doesn't exist. foreach m [set global::features] { if {![info exists index::feature($m)]} { set global::features [lremove ${global::features} $m] } elseif {[lindex [set index::feature($m)] 2] == 0} { package::activate $m } } } proc package::addPrefsDialog {pkg} { global package::prefs alpha::noMenusYet lunion package::prefs $pkg if {![info exists alpha::noMenusYet]} { # we were called after start-up; build the menu now menu::buildSome packagePrefs } } ## # ------------------------------------------------------------------------- # # "alpha::package" -- # # Mimics the Tcl standard 'package' command for use with Alpha. # It does however have some differences. # # package require ?-exact? ?-extension -mode -menu? name version # package exists ?-extension -mode -menu? name version # package names ?-extension -mode -menu? # package uninstall name version # package vcompare v1 v2 # package vsatisfies v1 v2 # package versions ?-extension -mode -menu? name # package type name # package info name # package maintainer name version {name email web-page} # package modes # # Equivalent to alpha::mode alpha::menu and alpha::extension # # package mode ... # package menu ... # package extension ... # # For extensions only: # # package forget name version # ------------------------------------------------------------------------- ## proc alpha::package {cmd args} { global index::feature switch -- $cmd { "require" { set info [package::getInfo "exact loose"] global alpha::rebuilding if {[llength $info]} { if {!${alpha::rebuilding} && [set version [lindex $args 1]] != ""} { if {[info exists exact]} { if {[lindex $info 0] != $version} { error "requested exact $version, had [lindex $info 0]" } } elseif {[info exists loose]} { if {[alpha::package vcompare [lindex $info 0] $version] < 0} { error "requested $version or newer, had [lindex $info 0]" } } elseif {![alpha::package vsatisfies [lindex $info 0] $version]} { error "requested $version, had [lindex $info 0]" } } if {$type == "feature"} { global package::loaded alpha::noMenusYet \ errorCode errorInfo package::activate $name } return [lindex $info 0] } if {!${alpha::rebuilding}} { error "can't find package $name" } } "uninstall" { set name [lindex $args 0] if {[llength $args] > 2} { set version [lindex $args 1] global alpha::rebuilding if {${alpha::rebuilding}} { global rebuild_cmd_count index::uninstall pkg_file switch -- [set script [lindex $args 2]] { "this-file" { set script [list file delete $pkg_file] } "this-directory" { set script [list rm -r [file dirname $pkg_file]] } } set index::uninstall($name) [list $version $pkg_file $script] set args [lrange $args 3 end] if {[llength $args]} { eval alpha::package [lindex $args 0] $name $version [lrange $args 1 end] return } if {[info exists rebuild_cmd_count] && [incr rebuild_cmd_count -1] == 0} { return -code 11 } } } else { cache::read index::uninstall return [set index::uninstall($name)] } } "forget" { catch {unset index::feature($name)} } "exists" { if {[package::getInfo] != ""} {return 1} else {return 0} } "type" { if {[package::getInfo] != ""} {return $type} error "No such package" } "info" { if {[llength [set info [package::getInfo]]]} {return [concat $type $info]} error "No such package" } "maintainer" - "disable" - "help" { set name [lindex $args 0] if {[llength $args] > 2} { global alpha::rebuilding if {${alpha::rebuilding}} { set version [lindex $args 1] global rebuild_cmd_count index::$cmd set data [lindex $args 2] set index::${cmd}($name) [list $version $data] set args [lrange $args 3 end] if {[llength $args]} { eval alpha::package [lindex $args 0] $name $version [lrange $args 1 end] return } if {[info exists rebuild_cmd_count] && [incr rebuild_cmd_count -1] == 0} { return -code 11 } } } else { cache::read index::$cmd return [set index::${cmd}($name)] } } "versions" { set info [package::getInfo] if {[llength $info]} { return [lindex $info 0] } error "No such package" } "vcompare" { set c [eval package::_versionCompare $args] if {$c > 0 || $c == -3} { return 1 } elseif {$c == 0} { return 0 } else { return -1 } } "vsatisfies" { if {[lindex $args 0] == "-loose"} { set c [eval package::_versionCompare [lrange $args 1 end]] return [expr {$c >= 0 || $c == -3 ? 1 : 0}] } else { set c [eval package::_versionCompare $args] return [expr {$c >= 0 ? 1 : 0}] } } "names" { set names "" package::getInfo foreach type $which { if {[array exists index::${type}]} { eval lappend names [array names index::${type}] } } return $names } "mode" - "menu" - "feature" { eval alpha::$cmd $args } default { error "Unknown option '$cmd' to 'package'" } } } proc package::getInfo {{flags ""}} { uplevel [list set flags $flags] uplevel { set name [lindex $args 0] if {[regexp -- {-([^-].*)} $name "" which]} { if {[lsearch $flags $which] != -1} { set $which 1 set name [lindex $args 1] set args [lrange $args 1 end] return [package::getInfo $flags] } if {[lsearch {feature mode} $which] == -1} { error "No such flag -$which" } set name [lindex $args 1] set args [lrange $args 1 end] } else { set which {feature mode} } foreach type $which { if {$type != "feature"} {cache::read index::${type}} if {[info exists index::${type}($name)]} { return [set index::${type}($name)] } } return "" } } ## # ------------------------------------------------------------------------- # # "package::_versionCompare" -- # # This proc compares the two version numbers. It returns: # # 0 equal # 1 equal but beta/patch update # 2 equal but minor update # -1 beta/patch version older # -2 minor version older # -3 major version newer # -5 major version older # # i.e. >= 0 is basically ok, < 0 basically bad # # It works for beta, alpha, dev, fc and patch version numbers. # Any sequence of letters starting b,a,d,f,p are assumed to # represent the particular item. # # 2.4 > 1.5 > 1.4.3 > 1.4.3b2 > 1.4.3b1 > 1.4.3a75 > 1.4p1 > 1.4 # ------------------------------------------------------------------------- ## proc package::_versionCompare {v1 v2} { regsub -all -nocase {([a-z])[a-z]+} $v1 {\1} v1 regsub -all -nocase {([a-z])[a-z]+} $v2 {\1} v2 set v1 [split $v1 .p] set v2 [split $v2 .p] set i -1 set ret 0 set mult 2 while 1 { incr i set sv1 [lindex $v1 0] set sv2 [lindex $v2 0] if {$sv1 == "" && $sv2 == ""} { break } if {$sv1 == ""} { set v1 [concat 8 0 $v1] set v2 [concat 9 $v2] continue } elseif {$sv2 == ""} { set v1 [concat 9 $v1] set v2 [concat 8 0 $v2] continue } elseif {[regexp -nocase {[a-z]} "$sv1$sv2"]} { # beta versions foreach v {sv1 sv2} { if {[regexp -nocase {[a-z]} [set $v]]} { # f = 8, b = 7, a = 6, d = 5 regsub -nocase {([^a-z])f} [set $v] {\1 7 } $v regsub -nocase {([^a-z])b} [set $v] {\1 6 } $v regsub -nocase {([^a-z])a} [set $v] {\1 5 } $v regsub -nocase {([^a-z])d} [set $v] {\1 4 } $v } else { # release version = 8, so it is larger than any of the above append $v " 8" } } set v1 [eval lreplace [list $v1] 0 0 $sv1] set v2 [eval lreplace [list $v2] 0 0 $sv2] set mult 1 continue } if {$sv1 < $sv2} { set ret -1 ; break } if {$sv1 > $sv2} { set ret 1 ; break } set v1 [lrange $v1 1 end] set v2 [lrange $v2 1 end] } if {$i == 0} { # major version, return 0, -3, -5 return [expr {$ret * (-4*$ret + 1)}] } else { return [expr {$mult *$ret}] } } proc package::versionCheck {name vers} { set av [alpha::package versions $name] set c [package::_versionCompare $av $vers] if {$c < 0 && $c != -3} { error "The installed version $av of '$name' is too old. Version $vers was requested." } elseif {$c == -3} { error "The installed version $av of '$name' may not be backwards compatible with the requested version ($vers)." } } proc package::reqInstalledVersion {name exact? {reqvers ""}} { global index::feature # called from installer set msg " I suggest you abort the installation." if {[info exists index::feature($name)]} { if {[set exact?] == ""} {return} set av [alpha::package versions $name] if {[set exact?] == "-exact"} { if {[alpha::package versions $name] != $reqvers} { alertnote "The installed version $av of '$name' is incorrect. Exact version $reqvers was requested.$msg" } } else { set reqvers [set exact?] if {$reqvers != ""} { set c [package::_versionCompare $av $reqvers] if {$c < 0 && $c != -3} { alertnote "The installed version $av of '$name' is too old. Version $reqvers was requested.$msg" } elseif {$c == -3} { alertnote "The installed version $av of '$name' may not be backwards compatible with the requested version ($reqvers).$msg" } } } } else { alertnote "This package requires the prior installation of '$name'. It is not currently installed.$msg" } } proc package::checkRequire {pkg} { if {[catch {alpha::package require $pkg} error]} { global errorInfo ; echo $errorInfo if {[catch {alertnote "The '$pkg' package had an error starting up: $error"} ]} { alertnote "The '$pkg' package had an error starting up" echo $error } } } proc package::queryWebForList {} { global defaultAlphaDownloadSite remote::site PREFS set sitename [dialog::variable defaultAlphaDownloadSite "Query which site?"] set nm [file join ${PREFS} _pkgtemp] set siteurl [set remote::site($sitename)] catch {file delete $nm} message "Fetching remote list…" set type [url::fetch $siteurl $nm] package::okGotTheList $sitename } ## # ------------------------------------------------------------------------- # # "package::okGotTheList" -- # # Helper proc which we can also call if the listing was interrupted # half-way through. # ------------------------------------------------------------------------- ## proc package::okGotTheList {{sitename ""}} { global defaultAlphaDownloadSite remote::site PREFS remote::lastsite if {$sitename == ""} { if {[info exists remote::lastsite]} { set sitename ${remote::lastsite} unset remote::lastsite } else { set sitename [dialog::variable defaultAlphaDownloadSite "From which site did you get the list?"] } } set type [lindex [url::parse [set remote::site($sitename)]] 0] set nm [file join ${PREFS} _pkgtemp] if {![file exists $nm] || (![file writable $nm]) || (![file size $nm])} { alertnote "It looks like that application returned control\ to me before the download was complete (otherwise there was an error)\ -- probably Netscape/IE. When it's done, or if there was an error\ hit Ok." } if {![file exists $nm] || (![file writable $nm]) || (![file size $nm])} { alertnote "There was a problem fetching the list --- if it's still\ being downloaded (you hit Ok too early!), wait till it's done \ and then select 'Ok Got The List'\ from the downloads menu." set remote::lastsite $sitename enableMenuItem -m internetUpdates "Ok, Got The List" on error "Error fetching list of new packages" } else { enableMenuItem -m internetUpdates "Ok, Got The List" off } set fd [open $nm "r"] catch {set lines [split [read $fd] "\n\r"]} close $fd if {[catch [list remote::process${type}Listing $lines] listing]} { alertnote "Error interpreting list of new packages" error "Error interpreting list of new packages" } message "Processing list…" remote::processList $sitename $listing message "Updated remote package information." } proc package::active {pkg {text ""}} { global global::features mode::features mode if {[lsearch -exact ${global::features} $pkg] != -1 \ || ($mode != "" && ([lsearch -exact [set mode::features($mode)] $pkg] != -1))} { if {[llength $text]} { return [lindex $text 0] } else {return 1 } } else { if {[llength $text]} { return [lindex $text 1] } else {return 0 } } } proc package::_editSite {{name ""} {loc ""}} { if {$name == ""} { set title "Name of new archive site" set name "Ken's Alpha site" set loc "ftp://ftp.ken.com/pub/Alpha/" } else { set title "Archive site name" } set y 10 set yb 105 set res [eval dialog -w 420 -h 135 \ [dialog::textedit $title $name 10 y 40] \ [dialog::textedit "URL for site" $loc 10 y 40] \ [dialog::okcancel 250 yb 0]] if {[lindex $res 3]} { error "Cancel" } # cancel was pressed return [lrange $res 0 1] } proc package::addIndex {args} { global index::feature pkg_file cache::read index::feature foreach f [concat $args] { set pkg_file $f message "scanning $f…" catch {source $f} } cache::create index-extension "variable" index::feature unset pkg_file } proc package::helpFile {pkg {pointer 0}} { # read help file instead global HOME set v [alpha::package versions $pkg] if {[lindex $v 0] == "mode"} { set v [lindex $v 1] alertnote "The '$pkg' package is implemented by $v mode, and has no separate help. I'll display the help for that mode instead." set pkg $v } if {![catch {alpha::package help $pkg} res]} { if {[lindex [set help [lindex $res 1]] 0] == "file"} { if {$pointer} { return "Help for this package is located in \"[lindex $help 1]\"" } else { edit -r -c [file join ${HOME} Help [lindex $help 1]] } } elseif {[string index $help 0] == "\["} { if {$pointer} { return "You can read help for this package by holding 'shift' when\ryou select its name in the menu." } else { uplevel \#0 [string range $help 1 [expr {[string length $help] - 2}]] } } else { if {$pointer} { return $help } else { new -n "* '$pkg' Help *" -info \ "Help for package '$pkg', version [alpha::package versions $pkg]\r$help" } } return } if {!$pointer} { alertnote "Sorry, there isn't a help file for that package. You should contact the package maintainer." } return } ## # ------------------------------------------------------------------------- # # "package::helpFilePresent" -- # # Help files must be of the same name as the package (minus 'mode' or # 'menu'), but may have any combination of mode, menu, or help after # that name. Whitespace is irrelevant. # ------------------------------------------------------------------------- ## proc package::helpFilePresent {args} { set res "" cache::read index::help foreach pkg $args { lappend res [info exists index::help($pkg)] } return $res } proc package::helpOrDescribe {pkg} { if {[set mods [expr {[getModifiers] & 0xfe}]]} { if {$mods & 34} { package::helpFile $pkg } else { package::describe $pkg } return 1 } return 0 } # ◊◊◊◊ Specific to 'features' ◊◊◊◊ # proc package::addRelevantMode {_feature mode} { global index::feature if {[info exists index::feature($_feature)]} { if {[lsearch -exact [set oldm [lindex [set index::feature($_feature)] 1]] $mode] != -1} { return } lappend oldm $mode set index::feature($_feature) \ [lreplace [set index::feature($_feature)] 1 1 $oldm] } else { set index::feature($_feature) [list [list "mode" $mode] $mode] } } proc package::removeRelevantMode {_feature mode} { global index::feature if {[info exists index::feature($_feature)]} { if {[set idx [lsearch -exact [set oldm [lindex [set index::feature($_feature)] 1]] $mode]] == -1} { return } set oldm [lreplace $oldm $idx $idx ""] set index::feature($_feature) \ [lreplace [set index::feature($_feature)] 1 1 $oldm] } } ## # ------------------------------------------------------------------------- # # "package::onOrOff" -- # # Complicated procedure to accomplish a relatively simple task! # # Given a list of packages from chosen in a dialog, possibly with # '-' prefixes to indicate 'off', work out what changes have to # be made to the set of on/off features to synchronise everything. # # If 'global' that means the list was of the global packages rather # than those for the current mode. # ------------------------------------------------------------------------- ## proc package::onOrOff {pkgs {lastMode ""} {global 0}} { global mode::features global::features set oldfeatures "" set offfeatures "" set onfeatures "" set newfeatures "" foreach m $pkgs { if {[string index $m 0] == "-"} { set m [string range $m 1 end] if {[lsearch -exact ${global::features} $m] >= 0} { lappend offfeatures $m } } else { if {[lsearch -exact ${global::features} $m] < 0} { lappend newfeatures $m } } } if {$global} { # turn off those which aren't there set offfeatures [lremove -l [set global::features] $pkgs] } if {[info exists mode::features($lastMode)]} { foreach m [set mode::features($lastMode)] { if {[string index $m 0] == "-"} { set m [string range $m 1 end] if {$global} { lappend oldfeatures $m } else { if {[lsearch -exact ${global::features} $m] >= 0} { if {[set ip [lsearch -exact $offfeatures $m]] < 0} { lappend newfeatures $m } else { set offfeatures [lreplace $offfeatures $ip $ip] } } } } else { if {$global} { if {[set ip [lsearch -exact $offfeatures $m]] >= 0} { set offfeatures [lreplace $offfeatures $ip $ip] } } else { if {[lsearch -exact ${global::features} $m] < 0} { lappend oldfeatures $m if {[lsearch -exact $newfeatures $m] < 0} { lappend offfeatures $m } } } } } } foreach m $newfeatures { if {[lsearch -exact $oldfeatures $m] < 0} { lappend onfeatures $m } } return [list $offfeatures $onfeatures] } proc package::partition {{mode ""}} { global index::feature set a "" set b "" set c "" if {$mode == ""} { # global case foreach n [lsort -ignore [alpha::package names]] { if {[info exists index::feature($n)]} { switch -- [lindex [set index::feature($n)] 2] { "1" { lappend a $n } default { lappend b $n } } } else { lappend c $n } } return [list $a $b $c] } else { set d "" set e "" set f "" set partition [array names index::feature] if {$mode == "global"} { set mode "global*" set search "-glob" } else { set search "-exact" global global::features set partition [lremove -l $partition ${global::features}] } foreach n [lsort -ignore $partition] { set ff [set index::feature($n)] switch -- [lindex $ff 2] { "1" { if {[lsearch $search [lindex $ff 1] $mode] != -1} { lappend a $n } elseif {[lsearch -exact [lindex $ff 1] "global"] != -1} { lappend b $n } elseif {[lindex $ff 1] != "global-only"} { lappend c $n } } "0" { if {[lsearch $search [lindex $ff 1] $mode] != -1} { lappend d $n } elseif {[lsearch -exact [lindex $ff 1] "global"] != -1} { lappend e $n } elseif {[lindex $ff 1] != "global-only"} { lappend f $n } } } } return [list $a $b $c $d $e $f] } } proc package::describe {pkg {return 0}} { set info [alpha::package info $pkg] set type [lindex $info 0] set v [alpha::package versions $pkg] if {[lindex $v 0] == "mode"} { set v [lindex $v 1] set msg "Package '$pkg', designed for use by $v mode is a" } else { set msg "Package '$pkg', version $v is a" } switch -- $type { "feature" { switch -- [lindex $info 3] { "0" { append msg " $type, and is [package::active $pkg {active inactive}]." } "1" { append msg " menu, and is " global global::menus if {![lcontains global::features $pkg]} { append msg "not " } append msg "in use." } "-1" { append msg "n autoloading $type." } } } "mode" { append msg " $type; modes are always active." } } cache::read index::maintainer if {[info exists index::maintainer($pkg)]} { set p [lindex [set index::maintainer($pkg)] 1] append msg "\rMaintainer: [lindex $p 0], [lindex $p 1]\r" append msg [lindex $p 2] } if {$return} { return $msg } # let package tell us where its prefs are stored. global alpha::prefs if {[info exists alpha::prefs($pkg)]} { set pkgpref [set alpha::prefs($pkg)] } else { set pkgpref $pkg } global ${pkgpref}modeVars if {[array exists ${pkgpref}modeVars]} { append msg "\r\r" [mode::describeVars $pkg $pkgpref] new -n "* <$pkg> description *" -m Tcl -info $msg } else { alertnote $msg } } proc package::deactivate {pkg} { global index::feature try::level \#0 [lindex [set index::feature($pkg)] 5] -reporting log -while "deactivating $pkg" } proc package::activate {pkg} { global index::feature if {[set init [lindex [set index::feature($pkg)] 3]] != ""} { message "Loading package '$pkg'…" try::level \#0 $init -reporting log -while "initialising $pkg" set index::feature($pkg) [lreplace [set index::feature($pkg)] 3 3 ""] } try::level \#0 [lindex [set index::feature($pkg)] 4] -reporting log -while "activating $pkg" } proc package::uninstall {} { cache::read index::uninstall if {![llength [set pkgs [array names index::uninstall]]]} { alertnote "I don't know how to uninstall anything." return } set pkg [dialog::optionMenu "Permanently remove which package/mode/menu?" [lsort -ignore $pkgs]] if {![dialog::yesno "Are you absolutely sure you want to uninstall $pkg?"]} { return } global pkg_file set pkg_file [lindex [set index::uninstall($pkg)] 1] set script [lindex [set index::uninstall($pkg)] 2] if {[regexp "rm -r\[^\r\n\]*" $script check]} { if {![dialog::yesno "This uninstaller contains a recursive removal command '$check'. Do you want to do this?"]} { return } } if {[catch "uplevel \#0 [list $script]"]} { alertnote "The uninstaller had problems!" } if {[dialog::yesno "All indices must now be rebuilt.\rShall I do this for you?"]} { alpha::rebuildPackageIndices rebuildTclIndices } else { alertnote "This will probably cause problems." } if {[dialog::yesno "It is recommended that you quit and restart Alpha. Quit now?"]} { quit } } ## # ------------------------------------------------------------------------- # # "date::isOlder" -- # # {Aug 22 1996} {Mar 26 22:17} # # We assume the format is 'Month Day Year' or 'Month Day Time', where # a time is distinguished by the presence of a colon. Months have # to be the standard three letter abbreviation (seems ok for all # ftp and http servers I've come across) # ------------------------------------------------------------------------- ## proc date::isOlder {a b} { if {$a == $b} { return 0 } regexp {(\w+)[ \t]+(\w+)[ \t]+([\w:]+)} $a "" am ad ay regexp {(\w+)[ \t]+(\w+)[ \t]+([\w:]+)} $b "" bm bd by # check year regexp {[0-9]+$} [lindex [mtime [now] abbrev] 0] thisy if {$ay == $thisy} { set ay "00:00" } if {$by == $thisy} { set by "00:00" } set a_ist [regexp : $ay] set b_ist [regexp : $by] if {!$a_ist && !$b_ist} { if { $ay < $by } { return 1 } elseif {$by < $ay} { return 0} } if {$a_ist && !$b_ist} { return 0 } if {!$a_ist && $b_ist} { return 1 } # both are a year or both are times and both in last year set months {Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec} # check we don't have a year wrap-around problem set now [lindex [mtime [now] short] 0] set refdate [lindex [mtime 2976439308 short] 0] if {$refdate == "4/26/98"} { # US regexp {([0-9]+)/([0-9]+)} $now "" now_m now_d } elseif {$refdate == "98-04-26"} { # Swedish regexp {[0-9]+-([0-9]+)-([0-9]+)} $now "" now_m now_d } else { # Other regexp {([0-9]+)[-/\.]([0-9]+)} $now "" now_d now_m } set am [lsearch $months $am] set bm [lsearch $months $bm] set aprev [expr {($now_m < $am || ($now_m == $am && $now_d < $ad))}] set bprev [expr {($now_m < $bm || ($now_m == $bm && $now_d < $bd))}] if {$aprev && !$bprev} {return 1} if {!$aprev && $bprev} {return 0} # both in same year: continue if {$am < $bm} { return 1 } elseif {$bm < $am} { return 0 } if {$ad < $bd} { return 1 } elseif {$bd < $ad} { return 0 } if {$a_ist && $b_ist} { regsub {:} $ay {.} ay regsub {:} $by {.} by if { $ay < $by } { return 1 } elseif {$by < $ay} { return 0} } # same ! return 0 } # ◊◊◊◊ Handle remote menu ◊◊◊◊ # proc package::menuProc {menu item} { global remote::site modifiedArrVars defaultAlphaDownloadSite switch -- $item { "Describe A Package" { set pkg [dialog::optionMenu "Describe which package?" \ [lsort -ignore [alpha::package names]]] package::describe $pkg } "Read Help For A Package" { set pkg [dialog::optionMenu "Read help for which package?" \ [lsort -ignore [alpha::package names]]] package::helpFile $pkg } "Uninstall A Package" { package::uninstall } "rebuildPackageIndex" { alpha::rebuildPackageIndices } "listPackages" { global::listPackages } "installBugFixesFrom" { # this item isn't in the menu by default anymore. set f [getfile "Select a bug-fix file…"] procs::patchOriginalsFromFile $f 1 } "Update List From A Web Archive Site" { package::queryWebForList } "Ok, Got The List" { package::okGotTheList } "Add Web Or Ftp Archive Site" { array set remote::site [package::_editSite] lappend modifiedArrVars remote::site } "Edit Web Or Ftp Archive Site" { set sitename [dialog::optionMenu "Edit which site?" \ [lsort -ignore [array names remote::site]]] array set remote::site \ [package::_editSite $sitename [set remote::site($sitename)]] lappend modifiedArrVars remote::site } "Remove Web Or Ftp Archive Site" { set sitename [dialog::optionMenu "Remove which site?" \ [lsort -ignore [array names remote::site]]] unset remote::site($sitename) lappend modifiedArrVars remote::site } "Describe Item" { alertnote "Select one of the packages, and I'll tell you\ when it was last modified, and from where it would be downloaded." } "Ignore Item" { alertnote "'Ignoring' a package tells me to remove it from\ new and updated package lists. It'll still be listed lower\ down in the menu" } "Select Item To Download" { alertnote "Select one of the packages, and it will be\ downloaded from its site on the internet, decompressed\ and installed." } default { remote::get $item } } } proc package::makeMenu {} { global remote::listing set l [list \ "Update List From A Web Archive Site…" \ "(Ok, Got The List" \ "<E<SRemove Web Or Ftp Archive Site…" \ "<S<BEdit Web Or Ftp Archive Site…" \ "<SAdd Web Or Ftp Archive Site…" "(-" \ "<S[menu::itemWithIcon {Describe Item} 81]" \ "<S<U[menu::itemWithIcon {Ignore Item} 81]" \ "<S[menu::itemWithIcon {Select Item To Download} 81]" ] foreach a ${remote::listing} { set type [lindex $a 1] regsub -all {\.(sit|bin|hqx)} [lindex $a 2] "" name lappend [lindex {other gone new uptodate update} [expr {$type + 2}]] $name if {$type == -1} { lappend disable $name } } if {[info exists update]} { lappend l "(-" "/\x1e(Updated items^[text::Ascii 79 1]" eval lappend l [lsort -ignore $update] } if {[info exists new]} { lappend l "(-" "/\x1e(New items^[text::Ascii 79 1]" eval lappend l [lsort -ignore $new] } if {[info exists uptodate]} { lappend l "(-" "(Current items" eval lappend l [lsort -ignore $uptodate] } if {[info exists other]} { lappend l "(-" "(Other items" eval lappend l [lsort -ignore $other] } if {[info exists gone]} { lappend l "(-" "(Vanished items" eval lappend l [lsort -ignore $gone] } Menu -n "internetUpdates" -m -p package::menuProc $l if {[info exists disable]} { foreach a $disable { enableMenuItem "internetUpdates" $a off } } } proc remote::processftpListing {lines} { set files {} foreach f [lrange [lreplace $lines end end] 1 end] { set nm [lindex $f end] if {[string length $nm]} { if {[string match "d*" $f]} { #lappend files "$nm/" } else { regexp {[A-Z].*$} [lreplace $f end end] time set date [lindex $time end] if {![regexp {^19[89][0-5]$} $date]} { # reject anything pre 1996 lappend files [list $nm $time] } } } } return $files } ## # ------------------------------------------------------------------------- # # "remote::processhttpListing" -- # # Extract all things like <A HREF="/~vince/pub/">Parent Directory</A> # followed by a date. Massage the date into 'Month day year'. # # I don't know if this will work for all http servers! It works for # mine. # ------------------------------------------------------------------------- ## proc remote::processhttpListing {lines} { set files {} foreach f $lines { if {[regexp {<A HREF="([^"]*)">.*</A>[ \t]*([^ \t]+)[ \t]} $f "" name date]} { if {![regexp {/$} $name]} { if {![regexp {[89][0-5]$} $date]} { # reject anything pre 1996 set date [split $date -] set md "[lindex $date 1] [lindex $date 0] " append md [expr {[lindex $date 2] < 80 ? 20 : 19}] append md [lindex $date 2] lappend files [list $name $md] } } } } return $files } proc remote::versionOneNewer {one two} { return 1 } proc remote::processList {sitename {l ""}} { global remote::listing modifiedVars # removed vanished items from the menu regsub -all {(\.|-)[0-9]+([a-zA-Z][0-9]+)?} $l "" ll foreach i ${remote::listing} { if {[string match "*${sitename}*" $i]} { regsub -all {(\.|-)([0-9]+([a-zA-Z][0-9]+)?)} \ [set ii [lindex $i 2]] "" ii if {[lsearch -glob $ll "$ii *"] == -1} { # it's vanished lappend removed $i lappend _removed [lindex $i 0] } } } if {[info exists removed]} { set remote::listing [lremove -l ${remote::listing} $removed] } # process new items foreach i $l { set namepart [lindex $i 0] set timepart [lindex $i 1] regsub -all {\.(sit|bin|hqx)} $namepart "" name regsub -all {(\.|-)[0-9]+([a-zA-Z][0-9]+)?} $name "" name if {[set idx [lsearch -glob ${remote::listing} "${name} *"]] != -1} { # update old item set item [lindex ${remote::listing} $idx] if {[lindex $item 2] != $namepart} { # it's changed set item [lreplace $item 1 end 2 $namepart $timepart $sitename] set remote::listing [lreplace ${remote::listing} $idx $idx $item] lappend _updated $name } elseif {[date::isOlder [lindex $item 3] $timepart]} { # date has changed set item [lreplace $item 1 end 2 $namepart $timepart $sitename] set remote::listing [lreplace ${remote::listing} $idx $idx $item] lappend _updated $name } } else { # new package lappend remote::listing [list $name 0 $namepart $timepart $sitename] lappend _new $name } } lappend modifiedVars remote::listing package::makeMenu ensureset _updated "none" ensureset _new "none" ensureset _removed "none" if {[catch {alertnote "Remote information, NEW: $_new, UPDATED: $_updated, REMOVED: ${_removed}."}]} { alertnote "Remote information, [llength $_new] new, [llength $_updated] updated and [llength $_removed] packages removed." } } proc remote::updateDatabase {idx val} { global remote::listing set item [lindex ${remote::listing} $idx] if {[lindex $item 1] != $val} { # it's changed set item [lreplace $item 1 1 $val] set remote::listing [lreplace ${remote::listing} $idx $idx $item] } } proc remote::pkgIndex {name} { global remote::listing if {[set i [lsearch -glob ${remote::listing} "${name} *"]] == -1} { set i [lsearch -glob ${remote::listing} \ "[string toupper [string index ${name} 0]][string range $name 1 end] *"] } return $i } proc remote::pkgDetails {name} { global remote::listing set idx [lsearch -glob ${remote::listing} "${name} *"] return [lindex ${remote::listing} $idx] } proc remote::get {pkg} { global remote::listing HOME remote::site downloadFolder file::separator # get pkg if {[set idx [remote::pkgIndex $pkg]] == -1} { alertnote "Sorry, I don't know from where to download that package." error "" } set item [lindex ${remote::listing} $idx] if {[set mods [expr {[getModifiers] & 0xfe}]]} { if {$mods & 34} { # just shift key demote the item in the hierarchy set itm [lindex $item 1] if {$itm == 0 || $itm == 2} { set itm 1 } else { set itm -2 } set item [lreplace $item 1 1 $itm] set remote::listing [lreplace ${remote::listing} $idx $idx $item] global modifiedVars lappend modifiedVars remote::listing package::makeMenu message "Package '$pkg' demoted." return } else { # describe the item alertnote "File '[lindex $item 2]', last modified [lindex $item 3], to be downloaded from [lindex $item 4], at [set remote::site([lindex $item 4])]" return } } set file [lindex $item 2] set sitename [lindex $item 4] # get the file if {![file exists $downloadFolder] || ![file isdirectory $downloadFolder]} { alertnote "Your Download Folder does not exist. I'll download to Alpha's home directory." set downloadFolder $HOME } if {[catch {url::fetchFrom [set remote::site($sitename)] ${downloadFolder}${file::separator} $file} err]} { alertnote "Fetch error '$err'" error "" } if {![file exists $file] || (![file writable $file]) || (![file size $file])} { alertnote "It looks like that application returned control to me before the download was complete (otherwise there was an error) -- probably Netscape/IE. When it's done, or if there was an error hit Ok." } # update database remote::updateDatabase $idx 1 package::makeMenu # install set filepre [lindex [split $file .] 0] # decode the downloaded file (this may happen automatically) set f_encoded [glob -nocomplain [file join ${downloadFolder} "${filepre}*{.hqx,.bin}"]] set f_stuffed [glob -nocomplain [file join ${downloadFolder} "${filepre}*.sit"]] if {[llength $f_encoded] == 1} { if {[llength $f_stuffed] == 1} { # downloader was set to decode automatically --- we must wait set ff [lindex $f_stuffed 0] while {![file writable $ff]} { switchTo 'SITx' } switchTo 'ALFA' } else { # downloader not set to decode automatically set ff [lindex $f_encoded 0] message "Decoding [file tail $ff]…" set name [file tail [app::launchFore SITx]] sendOpenEvent -r 'SITx' $ff } } # decompress the downloaded file (this may happen automatically) set f_stuffed [glob -nocomplain [file join ${downloadFolder} "${filepre}*.sit"]] set f_results [glob -t TEXT -nocomplain [file join ${downloadFolder} "${filepre}*"]] eval lappend f_results [glob -nocomplain "[file join ${downloadFolder} ${filepre}*]${file::separator}"] set f_results [eval lremove [list $f_results] $f_stuffed $f_encoded] if {[llength $f_results] == 0} { # we didn't decompress automatically set ff [lindex $f_stuffed 0] message "Decompressing [file tail $ff]…" set name [file tail [app::launchFore SITx]] sendOpenEvent -r 'SITx' $ff } # install set files [glob -t TEXT -nocomplain [file join ${downloadFolder} "${filepre}*"]] if {[llength $files] == 0} { # look for directory set dirs [glob -nocomplain "[file join ${downloadFolder} ${filepre}*]${file::separator}"] if {[llength $dirs] == 1} { set local [lindex $dirs 0] set files [glob -t TEXT -nocomplain "${local}*\[i|I\]{nstall,NSTALL}"] } else { set files "" set local $downloadFolder } } if {[llength $files] == 0} { alertnote "I can't find a suitable, unique install file. You must find it yourself." # open dir in finder openFolder $local return } if {[llength $files] > 1} { set f [listpick -p "Which file is the installer?" $files] } else { set f [lindex $files 0] } edit $f global mode if {$mode != "Inst"} { alertnote "I don't know what to do with this package from here." } else { if {[dialog::yesno "You can install this extension from the install menu.\rShall I do that for you?"]} { install::installThisPackage } } }